class BBCode
  @@codes = Hash.new
  @@smilies = Hash.new
  
  def initialize(content)
    @content = content
  end
  
  def to_html
    return @content if @content.blank?
    format_outside_code_tags(parse_tags(escape_html(@content)))
  end
  
  def parse_tags(content)
    content.gsub(tag_pattern) { |tag| parse_tag(tag, $1.downcase.to_sym, $2, $3) }
  end
  
  def parse_tag(tag, name, args, body)
    if @@codes.key? name
      @@codes[name].call(parse_tag_body(name, body), args.split(/\|/))
    else
      tag
    end
  end
  
  def parse_tag_body(name, body)
    if name == :code
      body
    else
      parse_tags(body)
    end
  end
  
  def format_outside_code_tags(content)
    content.split(/\<code[^>]*>.*?<\/code>/m).each do |match|
      content.gsub!(match, add_smilies(match.dup).gsub(/\n/, '<br />'))
    end
    content
  end
  
  def escape_html(content) # RESEARCHME There must be a better way to escape HTML and add smilies
    content.split(/\[code[^\]]*\].*?\[\/code\]/m).each do |match|
      content.gsub!(match, CGI::escapeHTML(match))
    end
    content
  end
  
  def add_smilies(content)
    # FIXME This is a terrible hack, there must be a better way!
    # The reason this is so complicated is because it's not considered the border (\b) unless there's an alpha-character
    @@smilies.each do |name, symbols|
      symbols.each do |symbol|
        content.gsub! symbol, "a#{symbol}b"
        content.gsub! /\ba#{Regexp.escape(symbol)}b\b/, "<img src=\"/images/smilies/#{name}.png\" alt=\"#{name}\" />"
        content.gsub! "a#{symbol}b", symbol
      end
    end
    content
  end
  
  def tag_pattern
    /\[([^\]=]+?)=?([^\]]*?)\](.*?)\[\/\1\]/m
  end
  
  def self.add_code(name, &block)
    @@codes[name] = block
  end
  
  def self.add_smiley(name, symbols = [])
    @@smilies[name] = symbols + ["\##{name}\#"]
  end
  
  def self.quote(author, content)
    "[quote=#{author}]#{content}[/quote]"
  end

  add_code(:b)      { |a, b| "<strong>#{a}</strong>" }
  add_code(:i)      { |a, b| "<em>#{a}</em>" }
  add_code(:quote)  { |a, b| "<blockquote>" + (b.empty? ? "" : "<h4>#{b} wrote:</h4>") + "#{a}</blockquote>"}
  add_code(:url)    { |a, b| b.empty? ? "<a href=\"#{a}\">#{a}</a>" : "<a href=\"#{b}\">#{a}</a>" }
  add_code(:img)    { |a, b| "<img src=\"#{a}\"" + (b.empty? ? "" : " alt=\"#{b}\" title=\"#{b}\"") + " />" }
  add_code(:code) do |a, b|
    language = b.first || :auto
    CodeRay.scan(a.chomp.reverse.chomp.reverse, language.to_sym).html(:line_numbers => :list)
    # LOL, there must be a better way to remove the first and last carriage returns
  end
  
  add_smiley 'smile',     %w[:) :-) =)]
  add_smiley 'wink',      %w[;) ;-)]
  add_smiley 'sad',       %w[:( :-( =(]
  add_smiley 'neutral',   %w[:| :-| =|]
  add_smiley 'tongue',    %w[:P :p :-P :-p =P =p]
  add_smiley 'big_smile', %w[:D :-D =D]
  add_smiley 'cool',      %w[B) B-)]
  add_smiley 'hmm',       %w[:/ :-/ :\\ :-\\]
  add_smiley 'yikes',     %w[:o :O :-o :-O =o =O]
  add_smiley 'lol'
  add_smiley 'mad'
  add_smiley 'roll'
end
